package com.tdk.wifidirectserver.socket

import android.os.SystemClock
import android.util.Log
import com.tdk.common.executor.AppExecutor
import com.tdk.common.util.ByteUtil
import com.tdk.wifidirectserver.socket.client.Client
import java.io.Closeable
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.Socket
import java.net.SocketException
import java.util.concurrent.ConcurrentLinkedQueue

abstract class SocketHandler(val client: Client) : Thread(
    "socket-${client.ip}"
) {
    private var mOutput: OutputStream? = null
    private var mInput: InputStream? = null

    @Volatile
    private var mRunning: Boolean
    private val mQueue = ConcurrentLinkedQueue<ByteArray>()
    private var mHeardTime: Long = -1

    //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 end
    private val mHeardBeatRunnable: Runnable?
    private val mSocket: Socket = client.socket!!

    /**
     * Instantiates a new Socket handler.
     *
     * @param socket the socket
     * @param ip     the ip
     */
    init {

        try {
            mInput = mSocket.getInputStream()
            mOutput = mSocket.getOutputStream()
        } catch (e: IOException) {

            e.printStackTrace()
        }
        mRunning = true
        mHeardBeatRunnable = Runnable {
//            sendHeardBeat()

        }
//        AppExecutor.instance.scheduledExecutor()
//            .postInterval(mHeardBeatRunnable, HEARD_INTERVAL_TIME, HEARD_INTERVAL_TIME)
        start()


    }

    val OutputRunnable: Runnable = Runnable {
        Log.d(TAG, "OutputRunnable mRunning:$mRunning ")
        while (mRunning) {
            if (!mSocket.isConnected || mSocket.isClosed) {
                Log.d(TAG, "SocketHandler has closed!")
                disconnect(client)
                finish()
                break
            }
            synchronized(this) {
                if (mRunning && !mQueue.isEmpty()) {
                    try {
                        val bytes = mQueue.poll()
                        Log.d(TAG, "send run: " + bytes.toList())
                        val packBytes: ByteArray = ByteUtil.packBytes(bytes)
                        mOutput!!.write(packBytes)
                        mOutput!!.flush()
                    } catch (ex: Exception) {
                        ex.printStackTrace()
                        Log.e(TAG, "Output write SocketException")
                        //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 start
                        if (ex is SocketException) {
                            mRunning = false
                            Log.e(TAG, "OutputRunnable set mRunning false")
                            mQueue.clear()
                            close(mInput)
                            close(mOutput)
                            close(mSocket)
                            disconnect(client)
                        }
                        //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 end
                    }
                }
            }

        }
    }

    val InputRunnable: Runnable = Runnable {
        Log.d(TAG, "InputRunnable mRunning:$mRunning ")
        while (mRunning) {
            Log.d(TAG, "mRunning:$mRunning ")
            if (!mSocket.isConnected || mSocket.isClosed) {
                Log.d(TAG, "SocketHandler has closed!")
                disconnect(client)
                finish()
                break
            }
            try {
                val bytes = ByteArray(1024)
                val result = mInput!!.read(bytes)
                if (result == -1) {
                    Log.d(TAG, "SocketHandler read -1")
                    disconnect(client)
                    finish()
                } else {
//                    val available = mInput!!.available()
//                    Log.d(TAG, "run:    available = $available")
//                    if (available > 0) {
//                        val bytes = ByteArray(available)
//                        mInput!!.read(bytes)
                        val realBytes = bytes.copyOf(result)
                        Log.d(TAG, "tag run get: " + realBytes.size)
                        val list: List<ByteArray> = ByteUtil.parseBytes(realBytes)
                        for (i in list.indices) {
                            parseData(list[i], client)
//                        }
                    }
                }
            } catch (e: IOException) {
                e.printStackTrace()
                if (e is SocketException) {
                    mRunning = false
                    Log.e(TAG, "InputRunnable set mRunning false")
                    mQueue.clear()
                    close(mInput)
                    close(mOutput)
                    close(mSocket)
                    disconnect(client)
                }
                Log.e(TAG, "Output write IOException")
            }
        }
    }

    override fun run() {
        Log.d(TAG, "run: ")
        // make thread priority default
        //Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        mRunning = true
        AppExecutor.instance.scheduledExecutor().postInterval(OutputRunnable, 0, 1000)
        AppExecutor.instance.scheduledExecutor().postInterval(InputRunnable, 0, 1000)
    }

    private fun sendHeardBeat() {
        Log.d(TAG, "sendHeardBeat: ")
        val bytes: ByteArray = ByteUtil.packHeartBytes()
        putOutMessage(bytes)
    }

    private fun checkHeardBreaked() {
//        if (SystemClock.elapsedRealtime() - mHeardTime > HEARD_BREAK_TIME) {
//            onBeatBreak(mIp)
//        }
    }

    private fun heardBeat() {
        Log.d(TAG, " heardBeat: ")
        mHeardTime = SystemClock.elapsedRealtime()
    }

    /**
     * Put out message (support for multi-thread)
     *
     * @param msg the msg
     */
    fun putOutMessage(msg: ByteArray) {
        Log.d(TAG, "putOutMessage:$msg")
        mQueue.offer(msg)
    }

    /**
     * Finish the socket thread. (after output msg has all sended)
     * send All msg, then finish
     */
    fun finish() {
        if (mHeardBeatRunnable != null) {
            AppExecutor.instance.scheduledExecutor().cancel(mHeardBeatRunnable)
        }
        mRunning = false
        Log.e(TAG, "finish set mRunning false")
        AppExecutor.instance.doOnWorkThread {
            try {
                synchronized(this) {
                    while (!mQueue.isEmpty()) {
                        Log.d(TAG, "finish in work: ")
                        val bytes = mQueue.poll()
                        Log.d(TAG, "send run finish: " + String(bytes))
                        val packBytes: ByteArray = ByteUtil.packBytes(bytes)
                        mOutput!!.write(packBytes)
                        mOutput!!.flush()
                    }
                }
            } catch (ex: Exception) {
                ex.printStackTrace()
                Log.e(TAG, "Output write SocketException")
            } finally {
                // add delay time to avoid msg receive or output failed
                AppExecutor.instance.scheduledExecutor().postDelay({
                    close(mInput)
                    close(mOutput)
                    close(mSocket)
                    disconnect(client)
                }, 200)
            }
        }
    }

    fun close(closeable: Closeable?) {
        if (null != closeable) {
            try {
                if (closeable is Socket) {
                    closeable.shutdownInput()
                }
                closeable.close()
            } catch (e: IOException) {
                Log.d("SafeCloseUtil", "SafeCloseUtil close: " + e.message)
            }
        }
    }

    /**
     * Parse data.
     *
     * @param bytes the bytes
     * @param srcIp the src ip
     */
    abstract fun parseData(bytes: ByteArray?, client: Client)

    /**
     * Disconnect.
     *
     * @param srcIp the src ip
     */
    abstract fun disconnect(client: Client)

//    /**
//     * onBeatBreak
//     *
//     * @param srcIp srcIp
//     */
//    protected abstract fun onBeatBreak(srcIp: String?)

    companion object {
        private const val TAG = "SocketHandler"
        const val HEARD_INTERVAL_TIME = 400L

        //SDD: modify for LEQHLJQ-1370 by linhui.shi 20210512 start
        //time 3000 -> 2100 reduce heat check time to avoid reconnect when in heat checking;
        const val HEARD_BREAK_TIME = 2100L
    }
}